import fs from 'fs';
import path from 'path';
import archiver from 'archiver';
import { Config } from '../app';
import { makeLabelLogger } from './logger';
import watch from 'node-watch';
import express = require('express');
import { sleep } from '../util/tools';
import { inspect } from 'util';
import { bootEvent } from './../boot';
const md5 = require('md5');
const md5FileP = require('md5-file/promise');

let { llog } = makeLabelLogger('asset');
let assetDir: string;
let fileList: FileInfoList;
let fileMap: Map<string, { path: string; md5: string }>;
bootEvent.on('appInit', app => {
	llog('asset init..');
	setupResUpdate(app);
});
export let createZip = async (dir: string, files: Array<string>) => {
	let md5List = files.map(v => {
		return fileMap[v].md5;
	});
	let zipMD5 = md5(md5List.join(','));
	llog(files, md5List, zipMD5);
	let sDir = Config.getStaticDirectory() + '/assetsZip';
	if (!fs.existsSync(sDir)) fs.mkdirSync(sDir);
	let zipPath = `${sDir}/${zipMD5}.zip`;
	if (fs.existsSync(zipPath)) {
		llog('use exist zip:', zipPath);
		return zipPath;
	} else {
		llog('create new zip:', zipPath);
		let output = fs.createWriteStream(zipPath);
		let archive = archiver('zip', {
			zlib: { level: 9 },
		});
		await new Promise((fill, reject) => {
			output.on('close', () => {
				llog('create zip finish');
				fill();
			});
			archive.on('error', reject);
			archive.pipe(output);
			files.forEach(v => {
				llog('zipfile', v);
				archive.file(`${dir}/${v}`, { name: v });
			});
			archive.finalize();
		});
		return zipPath;
	}
};

type FileInfo = {
	path: string;
	size: number;
	md5?: string;
};
type FileInfoList = Array<FileInfo>;

export let rebuildFileList = async (dir: string = assetDir) => {
	let now = Date.now();
	let genPath = `${dir}/filelist`;
	// llog('genfilelist', dir, genPath, now);

	let files = await getAllFiles(dir);
	// console.log(files);
	let pros = files.map(v => md5FileP(v.path));
	let md5s = await Promise.all(pros);
	files.forEach((v, k) => {
		v.md5 = md5s[k];
		v.path = v.path.replace(dir + '/', '');
	});
	// console.log('file md5', files);

	llog('rebuildFileList', dir, files.length);

	fileMap = new Map();
	fileList = files;
	fileList.forEach(v => {
		fileMap[v.path] = v;
	});
	fs.writeFileSync(
		genPath,
		JSON.stringify(
			{
				files,
				generateTime: now,
			},
			null,
			4,
		),
	);
};

// 注意，这里要避免所有在执行过程又动态创建的文件 ，否则会导致无限循环
let ignorePats = [/meta/, /filelist/, /jgameinfo/, /\/\./];
export let isIgnoredFile = function(path) {
	for (let r of ignorePats) {
		if (r.test(path)) return true;
	}
	return false;
};
async function getAllFiles(dir: string): Promise<Array<FileInfo>> {
	let files = [];
	let walk = require('walk').walk;
	await new Promise(fill => {
		let walker = walk(dir, {
			followLinks: true,
		});
		walker.on('file', (root, fileStats, next) => {
			root = root.replace(/\\/g, '/'); // for windows
			let path = root + '/' + fileStats.name;
			for (let r of ignorePats) {
				if (r.test(path)) return next();
			}
			files.push({
				path: root + '/' + fileStats.name,
				size: fileStats.size,
			});
			next();
		});
		walker.on('end', fill);
	});
	return files;
}

let cachedAppList = null;
export let getAppList = async (refresh = false) => {
	llog('get app list');
	if (!refresh && cachedAppList) return cachedAppList;

	let pathIPA = `/Apps/IOS/mj.ipa`;
	let outputIPA = Config.getStaticDirectory() + pathIPA;
	let pathAPK = `/Apps/Android/mj-release.apk`;
	let outputAPK = Config.getStaticDirectory() + pathAPK;

	let appList = [];
	if (fs.existsSync(outputIPA)) {
		let extract = require('ipa-extract-info');
		let fd = fs.openSync(outputIPA, 'r');
		let info = await new Promise<any>(fill => {
			extract(fd, function(err, info, raw) {
				if (err) throw err;
				fill(info[0]);
			});
		});
		let st = fs.statSync(outputIPA);
		appList.push({
			name: 'ios',
			time: st.mtimeMs,
			size: st.size,
			version: info.CFBundleShortVersionString,
			url: '/static' + pathIPA,
			info,
		});
	}
	if (fs.existsSync(outputAPK)) {
		let info = await require('node-apk-parser-promise')
			.load(outputAPK)
			.then(r => {
				return r.readManifest();
			});
		// console.log(info);
		let st = fs.statSync(outputAPK);
		// console.log(st);
		appList.push({
			name: 'android',
			time: st.mtimeMs,
			size: st.size,
			version: info.versionName,
			url: '/static' + pathAPK,
		});
	}

	cachedAppList = appList;
	return appList;
};

export let setupResUpdate = function(app) {
	assetDir = Config.getStaticDirectory() + '/assets';
	if (!fs.existsSync(assetDir)) fs.mkdirSync(assetDir, { recursive: true });

	if (fs.existsSync('../.git')) {
		llog('detecting source code, watch res update.');
		let resDir = (Config as any).getResDirectory();
		let resLink = assetDir + '/res';
		if (!fs.existsSync(resLink)) {
			llog(`make ${resLink} link to->${resDir}`);
			fs.symlinkSync(resDir, resLink, 'dir');
		}
		let tmGen: NodeJS.Timeout;
		watch([assetDir, resDir], { recursive: true }, (evt, name) => {
			// llog('file change1', evt, name);
			if (isIgnoredFile(name)) return;
			llog('file change2', evt, name);
			if (tmGen) clearTimeout(tmGen);
			tmGen = setTimeout(() => {
				rebuildFileList(assetDir);
				tmGen = undefined;
			}, 1000);
		});
	}

	rebuildFileList(assetDir);
	// getAppList();

	// 客户端获取最新文件表
	app.get('/filelist', (req, res: express.Response) => {
		let genPath = assetDir + '/filelist';
		llog('req filelist', genPath);
		res.setHeader('Content-Type', 'text/json');
		res.sendFile(genPath);
	});

	// 客户端发送文件列表，获取压缩包
	app.post('/getfiles', async (req: express.Request, res: express.Response) => {
		let files = req.body.toString().split(';');
		let accept = req.headers.accept;
		llog('req getfiles', files, accept);
		try {
			let genPath = await createZip(assetDir, files);
			if (accept == 'text/plain') {
				res.send(genPath);
			} else {
				await sleep(100);
				res.sendFile(genPath);
			}
		} catch (e) {
			llog('getfiles error', e);
		}
	});

	// 获取可下载的安装包信息
	app.get('/getAppList', async (req, res: express.Response) => {
		res.json(await getAppList());
	});

	// 用于ios safari安装的plist文件
	app.get('/ios.plist', async (req: express.Request, res: express.Response) => {
		let plist = require('plist');
		let content = fs.readFileSync('./html/ios.plist');
		let po = plist.parse(content.toString());
		let ios = (await getAppList())[0];
		// console.log(ios, po);
		// console.log(req.get('Host'), req.url, req.hostname);
		let host = req.protocol + '://' + req.get('host');
		po.items[0].assets[0].url = host + ios.url;
		po.items[0].metadata['title'] = ios.info.CFBundleDisplayName;
		po.items[0].metadata['bundle-identifier'] = ios.info.CFBundleIdentifier;
		po.items[0].metadata['bundle-version'] = ios.info.CFBundleShortVersionString;

		res.setHeader('Content-Type', 'text/json');
		res.send(plist.build(po));
	});

	app.get('/refreshres', (req: express.Request, res: express.Response) => {
		getAppList(true);
		res.send('ok');
	});
};
